/*

This file is part of Rooms3D.

Copyright (C) 2008 Pixecur Technologies Incorporated (Esotera).
Visit Esotera at http://www.esoteras3.com
 
Rooms3D is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 3, or (at your option)
any later version.
 
Rooms3D is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.
 
You should have received a copy of the GNU General Public License
along with Rooms3D; see the file COPYING.  If not, write to 
Pixecur Technologies, 213-31 Peet Street, St. John's, NL, A1B 3W8. 
 
Linking this library statically or dynamically with other modules is
making a combined work based on this library.  Thus, the terms and
conditions of the GNU General Public License cover the whole
combination.
 
*/

package com.datasentinel.rooms.file.sh3d;

import java.awt.Image;
import java.io.File;
import java.io.IOException;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.imageio.ImageIO;
import javax.media.j3d.BranchGroup;
import javax.swing.event.EventListenerList;

import com.datasentinel.rooms.file.FileThumbnailChangeListener;
import com.datasentinel.rooms.file.FileThumbnailStore;
import com.datasentinel.rooms.file.ThumbnailChangedEvent;
import com.datasentinel.rooms.j3d.util.Texturable;
import com.datasentinel.util.RunQueue;
import com.datasentinel.util.thumbnail.FileThumbnailer;
import com.datasentinel.util.thumbnail.ThumbnailException;

public class FilePieceOfFurnitureTextureManager
{
	private static Logger logger = Logger.getLogger(FilePieceOfFurnitureTextureManager.class.getName());
	private static FilePieceOfFurnitureTextureManager instance;
	private FileThumbnailer fileThumbnailer;
	private FileThumbnailStore thumbnailStore;
	private EventListenerList fileThumbnailChangeListenerList;
	private RunQueue thumbnailSaveQueue;
	private RunQueue thumbnailLoadingQueue;
	private static Image binaryFileImage;
	private static Image folderImage;
	private static Image temporaryFileImage;
	
	static
	{
		try
		{
			temporaryFileImage = ImageIO.read(FilePieceOfFurnitureTextureManager.class
					.getResource("file_temporary.png"));
			binaryFileImage = ImageIO
					.read(FilePieceOfFurnitureTextureManager.class.getResource("binary.png"));
			folderImage = ImageIO
			.read(FilePieceOfFurnitureTextureManager.class.getResource("folder.png"));
		}
		catch (IOException ioe)
		{
			throw new RuntimeException(ioe);
		}
	}

	public static synchronized FilePieceOfFurnitureTextureManager getInstance()
	{
		if (!(instance instanceof FilePieceOfFurnitureTextureManager))
		{
			instance = new FilePieceOfFurnitureTextureManager();
		}
		return instance;
	}

	private FilePieceOfFurnitureTextureManager()
	{
		super();
		fileThumbnailer = FileThumbnailer.getInstance();
		thumbnailStore = new FileThumbnailStore();
		fileThumbnailChangeListenerList = new EventListenerList();
		thumbnailSaveQueue = new RunQueue(5000L);
		thumbnailLoadingQueue = new RunQueue(5000L);
	}
	
	public Image getThumbnail(File file) throws ThumbnailException
	{
		try
		{
			if(file.isDirectory())
			{
				return folderImage;
			}
			else if(thumbnailStore.isThumbnailed(file))
			{
				return thumbnailStore.loadThumbnailImageForFile(file);
			}
			else throw new ThumbnailException("no such thumbnail exists for: "+file.getAbsolutePath());
		}
		catch(IOException ioe)
		{
			throw new ThumbnailException(ioe);
		}
	}

	public void updateThumbnail(BranchGroup toThumbnail)
	{
		enqueueThumbnailUpdate(toThumbnail);
	}
	
	public void cancel()
	{
		thumbnailLoadingQueue.cancel();
		// we don't want to cancel saves
		//thumbnailSaveQueue.cancel();
	}
	
	public void updateThumbnailFromNativeFile(final File toThumbnail, final File toUpdate)
	{
		try
		{
			if (toThumbnail.equals(toUpdate))
				return;
			ThumbnailChangedEvent thumbnailChanged = 
				new ThumbnailChangedEvent(this, toUpdate,temporaryFileImage);
			fireFileThumbnailChanged(thumbnailChanged);
			final Image image = fileThumbnailer.getThumbnail(toThumbnail);
			thumbnailChanged = 
				new ThumbnailChangedEvent(this, toUpdate,image);
			fireFileThumbnailChanged(thumbnailChanged);
			thumbnailSaveQueue.enqueue(new Runnable() { public void run() {
				try
				{
					thumbnailStore.saveThumbnailImageForFile(toUpdate, image);
				}
				catch(IOException ioe)
				{
					throw new RuntimeException(ioe);
				}
			}});
		}
		catch (Exception ioe)
		{
			throw new RuntimeException(ioe);
		}
	}
	
	public void duplicateThumbnail(final File from,final File to)
	{
		if(thumbnailStore.isThumbnailed(from))
		try
		{
			Image image = thumbnailStore.loadThumbnailImageForFile(from);
			ThumbnailChangedEvent thumbnailChanged = new ThumbnailChangedEvent(this,to,image);
			fireFileThumbnailChanged(thumbnailChanged);
			thumbnailSaveQueue.enqueue(new Runnable() { public void run() {
				thumbnailStore.duplicateThumbnail(from, to);
			}});
		}
		catch(IOException ioe)
		{
			throw new RuntimeException(ioe);
		}
	}

	public void removeThumbnail(File thatWasThumbnailed) throws ThumbnailException
	{
		try
		{
			thumbnailStore.removeThumbnailImageForFile(thatWasThumbnailed);
		}
		catch(IOException ioe)
		{
			throw new ThumbnailException(ioe);
		}
	}

	private void enqueueThumbnailUpdate(final BranchGroup branch)
	{
		HomeFilePieceOfFurniture homeFilePiece = null;
		File homeFile = null;
		if(branch instanceof Texturable)
		{
			homeFilePiece = (HomeFilePieceOfFurniture) branch.getUserData();
			homeFile = homeFilePiece.getFile();
		}	
		
		File toThumbnail = homeFile;
		ThumbnailQueueItem toEnqueue = null;
		if(toThumbnail.isDirectory())
		{
			toEnqueue = new ThumbnailQueueItem(this, branch) {
				@Override
				public Image getImage() throws ThumbnailException
				{
					return folderImage;
				}
			};
		}
		else 
		if (thumbnailStore.isThumbnailed(toThumbnail))
		{
			toEnqueue = new ThumbnailQueueItem(this, branch) {
				@Override
				public Image getImage() throws ThumbnailException
				{
					try
					{
						return thumbnailStore.loadThumbnailImageForFile(getFile());
					}
					catch (IOException ioe)
					{
						throw new ThumbnailException(ioe);
					}
				}
			};
		}
		else
		{
			toEnqueue = new ThumbnailQueueItem(this, branch) {
				@Override
				public Image getImage() throws ThumbnailException
				{
					Image image = null;
					if(!getFile().toURI().toString().startsWith("file:"))
					{
						image = binaryFileImage;
						logger.fine("cannot ask native thumbnailer to thumbnail non-native file!"+getFile());
						return image;
					}
					else
					{
						image = fileThumbnailer.getThumbnail(getFile());
					}
					final Image thumbnailImage = image;
					thumbnailSaveQueue.enqueue(new Runnable() { public void run() {
						try
						{
							thumbnailStore.saveThumbnailImageForFile(getFile(), thumbnailImage);
						}
						catch (Throwable ioe)
						{
							logger.log(Level.SEVERE, "Error saving thumbnail", ioe);
						}
					}});
					return thumbnailImage;
				}
			};
			
		}
		if(toEnqueue instanceof ThumbnailQueueItem)
			thumbnailLoadingQueue.enqueue(toEnqueue);
		else
			throw new RuntimeException("no thumbnail queue item was created for file: "+toThumbnail);
	}

	private void fireFileThumbnailChanged(ThumbnailChangedEvent thumbnailChanged)
	{
		Object[] fileThumbnailChangeListeners = fileThumbnailChangeListenerList.getListenerList();
		for (int i = fileThumbnailChangeListeners.length - 2; i >= 0; i -= 2)
		{
			if (fileThumbnailChangeListeners[i] == FileThumbnailChangeListener.class)
			{
				((FileThumbnailChangeListener)fileThumbnailChangeListeners[i + 1])
						.fileThumbnailChanged(thumbnailChanged);
			}
		}
	}

	public void addFileThumbnailChangeListener(FileThumbnailChangeListener listener)
	{
		fileThumbnailChangeListenerList.add(FileThumbnailChangeListener.class, listener);
	}

	public void removeFileThumbnailChangeListener(FileThumbnailChangeListener listener)
	{
		fileThumbnailChangeListenerList.remove(FileThumbnailChangeListener.class, listener);
	}

	abstract private static class ThumbnailQueueItem implements Runnable
	{
		private BranchGroup branch;
		private FilePieceOfFurnitureTextureManager thumbnailManager;
		
		public ThumbnailQueueItem(FilePieceOfFurnitureTextureManager newThumbnailManager, BranchGroup newBranch)
		{
			thumbnailManager = newThumbnailManager;
			branch = newBranch;
		}
		
		public File getFile()
		{
			return ((HomeFilePieceOfFurniture)branch.getUserData()).getFile();
		}
		
		public void run()
		{
			final File toThumbnail = getFile();
			ThumbnailChangedEvent thumbnailInProgress = new ThumbnailChangedEvent(branch,
					toThumbnail, temporaryFileImage);
			thumbnailManager.fireFileThumbnailChanged(thumbnailInProgress);
			Image image = binaryFileImage;
			try
			{
				logger.finer(String.format("calling getImage for [%s]", toThumbnail));
				Image thumbnailImage = getImage();
				if (thumbnailImage == null)
				{
					logger.warning(String.format("getImage() returned null for [%s]", toThumbnail));
					image = binaryFileImage;
				}
				else image = thumbnailImage;
				
			}
			catch (ThumbnailException te)
			{
				logger.log(Level.WARNING,String.format("error while getting thumbnail for [%s]", toThumbnail),te);
			}
			finally
			{
				ThumbnailChangedEvent thumbnailChanged = new ThumbnailChangedEvent(branch,
						toThumbnail, image);
				processThumbnailChangedEvent(thumbnailChanged);
			}
		}

		abstract public Image getImage() throws ThumbnailException;

		public void processThumbnailChangedEvent(ThumbnailChangedEvent thumbnailChanged)
		{
			thumbnailManager.fireFileThumbnailChanged(thumbnailChanged);
		}
	}
}
